/*****************************************************************************
 * jQuery Finder v0.8 - Makes lists into a Finder, similar to Mac OS X
 * 
 * @date		$Date: 2009-06-30 06:45:27 +0000 (Tue, 30 Jun 2009) $
 * @revision 	$Rev: 25 $
 * @author		$Author: rudas.n $
 * @copy		(c) Copyright 2009 Nicolas Rudas. All Rights Reserved.
 * @licence		MIT Licensed
 * @discuss		groups.google.com/group/jquery-en/browse_thread/thread/480bb6f722b66345
 * @issues		code.google.com/p/jqueryfinder/issues/
 * @latest		code.google.com/p/jqueryfinder/source
 * @demo		nicolas.rudas.info/jquery/finder
 * 
 *****************************************************************************
 * Syntax:
 *		$(selector).finder()					Create a new finder with default options
 *
 *		$(selector).finder(options)				Create a new finder with additional options
 *
 *		$(selector).finder(method,[arguments])	Execute a method on an existing finder
 *												-	select			Select item
 *												-	refresh			Reload currently selected item (cache is ignored)
 *												-	destroy			Completely remove finder
 *												
 **/

;(function($) {
	(function() {
		var f = function() {};
		(!window.console && (window.console = { log: f, warn:f, error:f, info: f, time:f, timeEnd: f, dir: f }));
	})();
	var debug = false;
	var debug_time = false;
	var random_number = function() {return Math.floor(Math.random()* new Date().valueOf() );  };
	
//	Check if scrollTo Plugin exists
	var scrollToPlugin = $.scrollTo || false;
	if(typeof scrollToPlugin == 'function') {
		scrollToPlugin = true;
		$.scrollTo.defaults.axis = 'xy'; }
	
	var Finders = $.Finders = {};
	
	var Finder = window.Finder = function Finder(element,opts) {
		var self = this;
		this.id = random_number();
		
		Finders[this.id] = this;
		
		this.methods = $.extend({},default_methods);
		
		for(var method in opts) {
		//	Any function passed in options, is saved as a method
		//	Allows for custom methods, e.g. this.method('my_method')
			if(typeof opts[method] === 'function') {
				this.method(method,opts[method]);
				delete opts[method]; }	};
		
		opts && (this.settings = $.extend({},defaults,opts));
			
		this.cycles = $.extend({},default_cycles);
		
		this.elements.original = element;
		
		this.settings.init && this.init();
		
		return this;
	};
	// ==============
	// = Properties =
	// ==============
	Finder.prototype.initialised = false;
	Finder.prototype.current_cycle = null;
	Finder.prototype.cache = {}; // stores received data
	Finder.prototype.current_container_width = 0; // changes as more columns are added
	Finder.prototype.widget_width = 0;
	Finder.prototype.elements = {
		original: null, // Reference to initial element - used when destroying Finder
		widget: null, // everything is in here (.ui-finder)
		title: null, // .ui-finder-title
		header: null, // .ui-finder-header
		wrapper: null, // wraps container (.ui-finder-wrapper), allows for scrolling
		container: null // columns are in here (.ui-finder-container)
	};
	
	// ================
	// = Core methods =
	// ================
	Finder.prototype.init = function() {
		debug_time && console.time('init');
		var self = this,
			settings = self.settings,
			settings_classes = settings.classNames,
			class_ui_finder = settings_classes['ui-finder'] || '',
			class_ui_finder_wrapper = settings_classes['ui-finder-wrapper'] || '',
			class_ui_finder_header = settings_classes['ui-finder-header'] || '',
			class_ui_finder_title = settings_classes['ui-finder-title'] || '',
			elements = {},
			header_contents = settings.header || ( self.methods.header )
													? self.method('header')
													: '',
			widget = elements.widget = (function() {
				var finder_dom = [
					'<div class="ui-finder '+class_ui_finder+'">',
						'<div class="ui-finder-title '+class_ui_finder_title+'"></div>',
						'<div class="ui-finder-header '+class_ui_finder_header+'">'+header_contents+'</div>',
						'<div class="ui-finder-wrapper '+class_ui_finder_wrapper+'" id="'+self.id+'">',
							'<div class="ui-finder-container">',
							'</div>',
						'</div>',
					'</div>'
				].join(''); //	'+elements.original[0].innerHTML+'\
				return $(finder_dom);
			})(),
			wrapper = elements.wrapper = $('.ui-finder-wrapper',widget),
			container =	elements.container = $('.ui-finder-container',widget),
			original = elements.original = this.elements.original;

		original
			.after(widget)
			.hide();

		elements.title = $('.ui-finder-title',widget);
		elements.header = $('.ui-finder-header',widget);
		
		this.elements = elements;
		
		this.widget_width = widget.width();

		$(window).bind('resize.ui-finder',function() {
		//	Need to keep track of widget's width so that column widths
		//	don't turn out messed up
		//	Should also keep track of widget parents?
			self.widget_width = widget.width();
		});			

		debug_time && console.timeEnd('init');
		
		container.bind('click.ui-finder-select',function(e) {
		//	Click event to handle showing of columns etc
		//	Bind click events to container so that only one event per finder is specified
			var settings = self.settings,
				target = e.target,
				$target = $(target),
				target_list_item = $target.closest('.ui-finder-list-item');

			if( !target_list_item.length ) { return; }

			debug_time && console.time('click');

			var type = (target.nodeName === 'A')
							? 'open'
							: 'select',
				target_anchor_item = target_list_item.children('a:first'),
				target_container = target_list_item.parents(settings.list_item_parent_node_type+':first'),
				target_column = target_list_item.parents('div.ui-finder-column'),
				data_target = target_list_item.data('ui-finder-data-target');
			
			debug_time && console.timeEnd('click');
			
			var cycle = self.cycle('select')
				.set({
					ret: false,
					type: type,
					event: e,
					url: target_anchor_item.attr('href'),
					elements: {
						list_item: target_list_item,
						list_container: target_container,
						anchor: target_anchor_item,
						column: target_column
					},
					data: {
						target: data_target
					}
				}).start();
						
			return cycle.ret;
		});
		
		self.method('onInit');

		self.cycle('select')		
		//	Initialise root
			.set('type','open')
			.set('url','root')
			.set('onEnd',function() {
				setTimeout(function() {
					 self.method('onRootReady');
				}, 0);
			})
			.set('FROM ROOT',true)
			.start();

		this.initialised = true;
		
		return this;
	};
	
	Finder.prototype.method = function(name,fn) {
		var self = this,
			method_exists = self.methods[name],
			args = Array.prototype.slice.call(arguments,1),
			ret = this;
		
		if(fn && typeof fn === 'function') { //set
			self.methods[name] = fn; }
			
		else if(method_exists && typeof method_exists === 'function') { // call
			ret = method_exists.apply(self,args); }
		
		return ret;
	};
	
	Finder.prototype.cycle = function(opts){
	//	summary: Create a new cycle
	//	A cycle is defined as a set of settings and actions to be performed
	//	before, during, and after an event
		var self = this,
			cycle = {
			id: random_number(),
			finder: this,
			type: null, // select, open, extend ...
			cache: this.settings.cache,
			autoScroll: true,
			event: null, // .target ..
			elements: {
				target: null,
				list_item: null,
				list_container: null,
				anchor: null,
				column: null
			},
			actions: {
				retrieve: true,
				process: true,
				display: true
			},
			data: {
				url: null, // fetch data from
				raw: null,  // server response or dom elements
				processed: null, // what comes out of the process method
				target: null // where should the data go
			},
			XMLHttpRequest: null, // if from ajax call
			set: function(k,v){
				if(typeof k === 'string'){
					this[k] = v;
				}
				else if (typeof k === 'object') {
					$.extend(this,k);
				}
				return this;
			},
			get: function(k){
				return this[k];
			},
			onStart: function(finder) {
				
			},
			onEnd: function(finder) {
				
			},
			onAbort: function(finder){
				
			},
			start: function(){
				self.current_cycle && self.current_cycle.abort();
				
				self.current_cycle = this;
				var start = this.onStart.call(this,self);
				if(start === false){ this.end(); return this; }
				
				this.actions.retrieve && self.retrieve(this);
				
				
				return this;
			},
			end: function(){
				self.current_cycle = null;
				this.onEnd.call(this,self);
				
				debug && console.info('end cycle',this);
			
				
				return this;
			},
			abort: function(){
							
				this.actions = {
					retrieve: false,
					process: false,
					display: false
				};
				
				this.XMLHttpRequest && this.XMLHttpRequest.abort();
				 
				this.end();
				
				this.onAbort.call(this,self);
			}
		};
	
		
		opts = (typeof opts === 'string') ? self.cycles[opts] : opts;
		opts = opts || {};
		return $.extend(cycle,opts);
	};
	
	Finder.prototype.retrieve = function(cycle) {
		var self = this,
			settings = self.settings,
			elements = self.elements,
			ajax_settings = settings.ajax,
			cache = self.cache,
			use_cache = cycle.cache,
			process_data = cycle.actions.process,
			retrieved_data;
		
		if (self.method('beforeRetrieve',cycle) === false || cycle.actions.retrieve === false) {
			cycle.end();
			return this; }

	//	First, check in cache for data
		retrieved_data = use_cache && cache[cycle.url];
	//	Then check in DOM
	//	Find other list items AND elements with class .ui-finder-contents
		retrieved_data = retrieved_data || (cycle.elements.list_item
							&& cycle.elements.list_item.children(settings.list_item_parent_node_type+',\
							 	.ui-finder-contents').clone());
		
	//	If still no data...
	//	...retrieve from URL
		if( cycle.url && cycle.url !== 'root'
			&& (!retrieved_data || (retrieved_data && retrieved_data.jquery && !retrieved_data.length)) ) {
		
			ajax_settings = $.extend({},ajax_settings,{
				url: cycle.url,
				success: function(response){
					cont(response);
				}
			});
			
			cycle.XMLHttpRequest = $.ajax(ajax_settings);
		}
	//	.. or use the original element	
		else if(cycle.url === 'root' && !retrieved_data)  {
			retrieved_data = elements.original.clone().appendTo(elements.container).show();
			cont(retrieved_data); }
			
	//	.. or continue, and see what happens
		else {
			cont(retrieved_data); }
		
		function cont(response){
			cycle.data.raw = response;
			
			if(self.settings.paging && cycle.event && cycle.event.type === 'scroll'
				&& self.method('scroll_check_data',cycle,response) === false) {
			//	Method scroll_check_data should return false if there's no more data
			//	In such case, ignore future scroll events on that column
				cycle.data.target.data('ui-finder-scroll-end',true);
				cycle.no_data(); cycle.end();
				return; }
			
			if (self.method('afterRetrieve',cycle,response) === false || cycle.actions.process === false) {
				cycle.end(); }
				
			else  {
				cycle.url && (cache[cycle.url] = cache[cycle.url] || response);
				self.process(cycle); }
		}
				
		return this;
	};
	
	Finder.prototype.process = function(cycle) {
		var self = this,
			settings = self.settings,
			ajax_settings = settings.ajax,
			display_data = cycle.actions.display,
			list_item_node_type = settings.list_item_node_type,
			list_item_parent_node_type = settings.list_item_parent_node_type,
			data_response;

		if (self.method('beforeProcess',cycle) === false || cycle.actions.process === false) {
			cycle.end();
			return this; }
		
		data_response = cycle.data.processed || cycle.data.raw;
		
		if (!data_response) {
			throw new Error('jQuery Finder: no data response',''); }

		debug_time && console.time('process_data');

		data_response = (!data_response.jquery) ? $(data_response) : data_response;

		if (!data_response[0] || !data_response[0].innerHTML) {
			throw new Error('jQuery Finder: Data response not jQuery/DOM element \
							(is '+ typeof data_response +'). Define the "beforeProcess" or "afterRetrieve" \
							callback and set "cycle.data.processed" to a jQuery/DOM element',''); }

		var list_items = (data_response.is(list_item_parent_node_type))
							? data_response.children(list_item_node_type)
							: data_response.children(list_item_parent_node_type)
								.eq(0).children(list_item_node_type);
		
		list_items.each(function(i){
		//	Process data. Find list items and add necessary classes and icons
			var $this = $(this),
				className = this.className,
				classNameTypeMatch = className.match(/ui\-finder\-(file|folder)/),
				thisType,thisTypeClass,
				anch = $this.children('a:first'),
				anchTitle = anch.attr('title') || '';

		// Get the type of this list item (file or folder)
			if(classNameTypeMatch && classNameTypeMatch[1]) {
				thisType = classNameTypeMatch[1]; }

		// If type (file or folder) is not specified try to figure it out
			else {
				if($this.children(list_item_parent_node_type).length) {
					this.className += ' ui-finder-folder';
					thisType = 'folder'; }
				else { // default type is file
					this.className += ' ui-finder-file';
					thisType = 'file';  } }

			thisTypeClass = settings.classNames['ui-finder-list-item-'+thisType];

			var span = document.createElement('span');
			span.className = settings.classNames['ui-finder-icon-folder-arrow'] + ' \
							ui-finder-icon ui-finder-icon-arrow';
			this.appendChild(span);
			this.className += ' ui-finder-list-item';
			
			if( !anch.length ){ // not necessary to have a link
				return; }
							
			span = document.createElement('span');
			span.className = 'ui-icon '+thisTypeClass+' ui-finder-icon ui-finder-icon-'+thisType;
			anch[0].appendChild(span);

			if(anchTitle.length == 0) {
			//	Just some usability fix
				anch.attr('title', anch.text() ); }
		});

		debug_time && console.timeEnd('process_data');

		cycle.data.processed = data_response;

		if (self.method('afterProcess',cycle) === false || cycle.actions.display === false) {
			cycle.end(); }
			
		else  {
			self.display(cycle); }

		return this;

	};
	
	Finder.prototype.display = function(cycle) {
		var self = this,
			settings = self.settings,
			elements = self.elements,
			wrapper = elements.wrapper,
			container = elements.container,
			data = cycle.data.processed,
			fix_width = cycle.type !== 'extend', // width should be fixed iff new columns are added
			column;

		if (self.method('beforeDisplay',cycle) === false || cycle.actions.display === false) {
			cycle.end();
			return this; }
		
		debug_time && console.time('display_data');

		if(cycle.data.target) { // column exists
			column = cycle.data.target.removeClass('ui-finder-hidden-column');
			if(cycle.type === 'extend') {
				column.append( cycle.data.processed );	}
		}		

		else {
			cycle.url = cycle.url || random_number();
			var column_id = cycle.url+''.replace(/[\W\s]*/g,'') + random_number(),
				column_depth_level = (function(){
					return (cycle.elements.column && parseInt(cycle.elements.column.attr('data-finder-list-level'),10) + 1)
							|| 0;
				})();

			column = document.createElement('div');

			column.id = column_id;
			column.setAttribute('data-finder-list-source',cycle.url);
			column.setAttribute('data-finder-list-level',column_depth_level);
			column.className = 'ui-finder-column ui-widget-content';
			column.style.position = 'absolute';
			column.style.zIndex = '0';
			column.style.left = '0px';
			
			data = data[0];
			data.style.display = 'block';
						
			column.appendChild(data);
			
			container[0].appendChild(column);

			column = cycle.data.target = $(column);
			
			var column_width = column.outerWidth(true);
			
			column_width = (column_width > self.settings.max_column_width)
							?	self.settings.max_column_width
							: 	column_width + 20;
			
			column.data('finder-width', column_width );	
			
		//	Save a reference to this new column to the list item that created it.
		//	This minimises DOM checks. Also avoids relating a column to a specific URL	
			cycle.elements.list_item && cycle.elements.list_item.data('ui-finder-data-target',column);
			
			if(!cycle.elements.list_item
				|| (cycle.elements.list_item && cycle.elements.list_item.hasClass('ui-finder-folder') ) ){
			//	Bind scroll event to columns that most probably contain list of items
			//	i.e. columns that originate from a list item of type folder
				column
					.unbind('scroll.ui-finder-scroll')
					.bind('scroll.ui-finder-scroll',function(e) {
						if(!self.settings.paging){return;}

						var $this = $(this),
							scrollTop = this.scrollTop, // how much did we scroll
							scrollHeight = this.scrollHeight, // how much is the scroll space
							offsetHeight =  this.offsetHeight, // how much is the height
							height_available = scrollHeight - offsetHeight; // how much scroll space available
						
					//	console.warn('scroll', $this.data('finder-scroll-ignore'),this )
						
						if( height_available != scrollTop
							|| $this.data('ui-finder-scroll-end')
							|| $this.data('finder-scroll-ignore')
							|| $this.data('finder-loading-next-page') ) {
								return; }
								
						self.cycle('scroll')
							.set({
								url: $this.attr('data-finder-list-source'),
								event: e,
								elements: {
									column: $this },
								data: {
									target: $this }												
							}).start();
					});
			}
		}
		
		fix_width && (self.current_container_width += column.data('finder-width'));

		fix_width && self.__adjust_width(cycle,true);
		
		debug_time && console.timeEnd('display_data');
				
		if (!fix_width || self.method('afterDisplay',cycle) === false ) {
			cycle.end(); }
			
		return this;
	};
	
	Finder.prototype.__adjust_width = function(cycle,endCycle) {
	//	Adjust the width of visible columns
	//	Right-most column must take-up all available space (if any)
	//	All others must fit to their contents (up-to maximum specified width)
	//	This is called whenever a new column is added
		debug_time && console.time('fix_width');
	//	console.error('adjust', cycle.data.target && cycle.data.target[0])
		var self = this,
			elements = self.elements,
			widget = elements.widget,
			wrapper = elements.wrapper,
			container = elements.container,
			settings = self.settings,
			
			animate = (!settings.animate || !isNaN(settings.animate)) ? settings.animate : 200,
			
			total_column_width = this.current_container_width,
			total_available_width = this.widget_width,
			
			data_target = cycle.data.target,
			data_target_left = data_target && data_target.data('finder-width'),
			
			scroll_to_column = false,
			scroll_to_column_duration = settings.scrollTo.column,
			scroll_to_item_duration = settings.scrollTo.item;
		
		if(total_column_width > total_available_width) {
		//	Need to make the x-axis scrollbar to appear and make
		//	some space for all the columns
			container.width( total_column_width  );
			scroll_to_column = scrollToPlugin && data_target; }
		
		else {
		//	Columns are all visible, no worries
			container.width( 'auto' ); }
		
		if( data_target ) {
		//	data_target is about to change its width. 
		//	This may cause the column to scroll and trigger the scroll event,
		//	which we don't want
			data_target = data_target
							.data('finder-scroll-ignore',true)
							.css('width',null)[0];
			data_target.style.left = parseFloat(total_column_width - data_target_left) + 'px';
			data_target.style.width = 'auto';
			data_target.style.position = 'absolute';
		
		//	Animate column's width
			if(animate
				&& (!cycle.elements.column
					|| cycle.elements.column && cycle.elements.column[0] !== cycle.data.target[0])) {
				
				$(data_target)
					.css('overflow-y','hidden') // avoid showing a scroll bar whilst animating
					.css('right',  container.width() - parseInt(data_target.style.left,10) )
					.animate({'right':0 },{
						duration:animate,
						complete:function(){
							endCycle && cycle.end();
							$(this)
								.data('finder-scroll-ignore',false)
								.css('overflow-y','scroll'); }
					}); }
			
			else {
				endCycle && cycle.end();
				data_target.style.right = 0;
			//	Its ok now for the column to scroll
				$(data_target).data('finder-scroll-ignore',false); }
		}
		
		debug_time && console.timeEnd('fix_width');
		
	//	Scroll to selected item
	//	Mostly useful if item not selected following direct user action (e.g. click event) 
		scrollToPlugin && cycle.autoScroll && cycle.elements.list_item
			&& cycle.elements.column.data('finder-scroll-ignore',true)
			&& cycle.elements.column.scrollTo(cycle.elements.list_item,{
				duration: scroll_to_item_duration,
				onAfter: function() {
					cycle.elements.column.data('finder-scroll-ignore',false);
				}
			});
		
		scroll_to_column
			&& wrapper.scrollTo(data_target,{ duration: scroll_to_column_duration || 0 });
						
		return this;
	};
	
	// =========================
	// = API or helper methods =
	// =========================
	Finder.prototype.select = function(urls,type,no_cache) {
		var self = this;
		urls = $.makeArray(urls);
		
		var url = urls.shift(),
			cycle = this.cycle('select_multiple');
		
		type && cycle.set('type',type);
		no_cache && cycle.set('cache',false);
		
		cycle
			.set('urls',urls)
			.set('url',url)		
			.start();
			
		return this;
	};
	Finder.prototype.path = function() {
		var self = this,
			ret = [],
			columns = $('.ui-finder-column:not(.ui-finder-hidden-column)',this.elements.container);
		
		columns.each(function() {
			var active = $('.ui-finder-list-item-active > a',this),
				url = (active.length && active.attr('href')) || $('.ui-finder-list-item',this).index(  $('.ui-finder-list-item-active',this) );
			
			if( url !== -1 ) {
				ret.push(url); }			
		});
				
		return ret;
	};
	Finder.prototype.title = function(val) {
		this.elements.title.html(val);
		return this;
	};
	Finder.prototype.header = function(val) {
		this.elements.header.html(val);
		return this;
	};
	Finder.prototype.destroy = function(){
		var self = this,
			elements = this.elements,
			original = elements.original,
			widget = elements.widget;
					
		widget.remove();
		
		original.show();
		
	// Delete reference to Finder
		delete Finders[this.id];
		
		return this;
	};
	Finder.prototype.refresh = function(type){
		var columns = $('.ui-finder-column:not(.ui-finder-hidden-column)',this.elements.container),
			active = $('.ui-finder-list-item-active.ui-finder-folder',columns),	
			current = $('.ui-finder-list-item',columns).index( active.eq( active.length - 1 )[0] );
		
		this.select(current,type,true);
		
		return this;
	};
	
	
	// ============
	// = Settings =
	// ============	
	var defaults = Finder.settings = (function() {
		return {
			title: '',
			init:true,
			root_url: false,
			paging: false,
			animate: 500,
			cache: true,
			ajax: {
				dataFilter: function(data,type) { return data; }
			}, 
			scrollTo: {	item: 500, column: 0 },
			max_column_width: 450,
			list_item_node_type: 'li',
			list_item_parent_node_type: 'ol,ul',
			classNames: {
				'ui-finder' : 'ui-widget ui-widget-header',
				'ui-finder-wrapper' : 'ui-widget-content',
				'ui-finder-header' : undefined,
				'ui-finder-title' : undefined,
				'ui-finder-list-item-active' : 'ui-state-default',
				'ui-finder-list-item-activeNow' : 'ui-state-hover',
				'ui-finder-list-item-file' : 'ui-icon-document',
				'ui-finder-list-item-folder' : 'ui-icon-folder-collapsed',
				'ui-finder-icon-folder-arrow' : 'ui-icon ui-icon-triangle-1-e'
			}
		};
	})();
	
	var default_methods = Finder.methods = (function() {
		return {
			onInit : function() {
				debug && console.error('INIT',arguments);
			},
			onRootReady  : function(cycle){
				debug && console.error('ROOT READY',arguments);
				// /console.dir(cycle)
			},
			onItemSelect : function(cycle){
				debug && console.error('on Item Select',arguments);
				return false;
			},	
			onItemOpen : function(cycle){
				debug && console.error('on Item Open',arguments);
		
				return false;
			},
			onFolderSelect : function(){
				debug && console.error('on Folder Select',arguments);
		
			},
			onFolderOpen : function(listItem,newColumn,self){
				debug && console.error('on Folder Open',arguments);
		
			},
			processData : function(responseText) {
				debug && console.error('Process Data',arguments);
				responseText = responseText || '';
				return $('<div class="ui-finder-content"/>').append(responseText);
			},
			beforeRetrieve: function(cycle){
				debug && console.error('on beforeRetrieve',arguments);
			},
			afterRetrieve: function(cycle,response){
				debug && console.error('on afterRetrieve',arguments);
		
			},
			beforeProcess: function(cycle){
				debug && console.error('on beforeProcess',arguments);
		
			},
			afterProcess: function(cycle){
				debug && console.error('on afterProcess',arguments);
		
			},
			beforeDisplay: function(cycle){
				debug && console.error('on beforeDisplay',arguments);
		
			},
			afterDisplay: function(cycle){
				debug && console.error('on afterDisplay',arguments);
			},
			scroll_placeholder: function(cycle){
				return '<p>Loading</p>';
			},
			scroll_set_page_url: function(cycle){
				return cycle.url;
			},
			scroll_check_data: function(cycle) {
				return true;
			}
		};
	})();
	
	var default_cycles = Finder.cycles = {}; (function() {
		default_cycles.select = {
			type: 'select',
			onStart: function(){
				var self = this.finder,
					cycle = this,
					elements = self.elements,
					settings = self.settings,
					wrapper = elements.wrapper,
					container = elements.container,
					list_item_node_type = settings.list_item_node_type,
					list_item_parent_node_type = settings.list_item_parent_node_type,

					scrollTo_item_duration = settings.scrollTo.item || 0,

					classes = settings.classNames,
					classes_active = classes['ui-finder-list-item-active'] || '',
					classes_active_now = classes['ui-finder-list-item-activeNow'] || '',
					columns = $('div.ui-finder-column:not(.ui-finder-hidden-column)',container);
				
				var	target_list_item = cycle.elements.list_item,
					target_anchor_item = cycle.elements.anchor,
					target_column = cycle.elements.column,
					target_column_depth_level = target_column && target_column.attr('data-finder-list-level'),
					target_container = cycle.elements.container,
					data_target = cycle.data.target;
			
				if(target_column && target_column.is('.ui-finder-hidden-column')) {
				// selecting different items at multiple levels can break the finder
				// if the target element of this cycle is not visible then
				// ignore this cycle
					cycle.end();
					return;
				}
			
				debug && console.error('URL:',cycle.url,cycle.elements.column);
			
				if(cycle.url !== null && cycle.url !== 'root'
					&& !cycle.elements.column) {
				 //	Triggered thru api or anchor has no link
					var event_target = cycle.url,
						$target;
					
					if(typeof event_target === 'number') {// find by index
						$target = $(list_item_node_type+'.ui-finder-list-item',columns).eq( event_target ); }
				
					else if( typeof event_target === 'string') {// find by href
						$target = $('.ui-finder-list-item > a[href="'+event_target+'"]',columns).eq(0); }
				
					if(!$target || !$target.length) {
						throw new Error('jQuery Finder: '+cycle.url+' not found','');
						cycle.abort();
						return false; }
				
					else {
						cycle.elements.list_item = target_list_item =
						 	$target.closest(list_item_node_type+'.ui-finder-list-item');
			
						cycle.elements.anchor = target_anchor_item = target_list_item.children('a:first');
				
						cycle.elements.container = target_container =
							target_list_item.parents(list_item_parent_node_type+':first');
				
						cycle.elements.column = target_column = target_list_item.parents('div.ui-finder-column');
				
						cycle.url = target_anchor_item.attr('href');
				
						target_column_depth_level = target_column && target_column.attr('data-finder-list-level'); }
				}	
			
				cycle.elements.column && cycle.elements.column.data('finder-scroll-ignore',true);
				
				if(cycle.url === 'root' && settings.root_url) {
				//	retrieve root
					cycle.url = settings.root_url;	} 
			
				if(target_column) {
				//	Currently selected item will no longer be active
					$(list_item_node_type+'.ui-finder-list-item-activeNow',container)
						.removeClass('ui-finder-list-item-activeNow ' + classes_active_now  );	

				//	Neither will active items in the same column as the current one
					$(list_item_node_type+'.ui-finder-list-item-active',target_column)
						.removeClass('ui-finder-list-item-active ' + classes_active  );	

					columns.each(function(){
					//	Hide columns that are at lower level
					//	and fix the width of those that will be visible
						var column = $(this),
							column_depth_level = column.attr('data-finder-list-level');
						
						column.data('finder-scroll-ignore',true);
						
						this.style.position = 'relative';
						this.style.left = null;
						this.style.right = null;
						this.style.zIndex = null;
						this.style.width = column.data('finder-width') + 'px';
						
						column.data('finder-scroll-ignore',false);
						
						if( column_depth_level > target_column_depth_level ) {
							$(list_item_node_type+'.ui-finder-list-item-active',column)
								.removeClass('ui-finder-list-item-active ' + classes_active ); 

							self.current_container_width -= column.data('finder-width');
							column.addClass('ui-finder-hidden-column'); }
					});			
					
					target_column.data('finder-scroll-ignore',true);
					
				//	Style selected list item
					target_list_item
						.addClass('	ui-finder-loading\
									ui-finder-list-item-active\
									ui-finder-list-item-activeNow '+ classes_active+' '+classes_active_now);				
				}
				
				debug_time && console.timeEnd('select');
				
			//	Callbacks (for backword compatibility)
				var ret;
			
				if (target_list_item && target_list_item.is('.ui-finder-file') ){
					if(cycle.type === 'select'){
						ret = self.method('onItemSelect',cycle); }

					else if(cycle.type === 'open'){
						ret = self.method('onItemOpen',cycle); }  }
						
				else if (target_list_item && target_list_item.is('.ui-finder-folder') ){
					if(cycle.type === 'select'){
						ret = self.method('onFolderSelect',cycle); }

					else if(cycle.type === 'open'){
						ret = self.method('onFolderOpen',cycle); } }
			
				cycle.ret = ret || false;
			
				if(ret === false || ret === true) {
				//	Returning true onOpen callbacks, default browser action occurs (link is followed)
				//	Returning false, item is just selected, no new columns added
					cycle.data.target = target_column;
					self.__adjust_width(cycle); cycle.abort(); }
			},
			onEnd: function(){
				this.elements.list_item && this.elements.list_item.removeClass('ui-finder-loading');
			//	this.elements.column && this.elements.column.data('finder-scroll-ignore',false);
			},
			onAbort: function() {

			},
			autoScroll: false
		};
	
		default_cycles.select_multiple = {
			type: 'select',
			urls: [],
			event: {},
			autoScroll: true,
			onStart: default_cycles.select.onStart,
			onEnd: function() {
				default_cycles.select.onEnd.apply(this);
			
				var next_url = this.urls.length && this.urls.shift();
			
				if(!next_url){return;}
			
				var new_cycle = $.extend({},default_cycles.select_multiple,{
					url: next_url,
					urls: this.urls
				});
				var self = this;
				setTimeout(function() {
					self.finder.cycle(new_cycle).start();
				}, 50);
			},
			onAbort: default_cycles.select.onAbort
		};
	
		default_cycles.scroll = {
			type: 'extend', // select, open, extend ...
			actions: {process: true,retrieve: true,display: true},
			cache: false,
			no_data: function() {
				var $this = this.elements.column;
				if(!$this){return;}
			
				this.placeholder && this.placeholder.replaceWith('<p>No data</p>');
				delete this.placeholder;
			},
			onStart: function(){
				var $this = this.elements.column;
				
				if(!$this){return;}
	
				$this.data('finder-loading-next-page',true);
			
				var placeholder = this.finder.method('scroll_placeholder',this);
				this.placeholder = $(placeholder).appendTo($this);
			
				$this.scrollTo(this.placeholder);
			
				var url = this.finder.method('scroll_set_page_url',this);
			
				this.url = url;

			},
			onEnd: function(){
				
				var $this = this.elements.column;
				if(!$this){return;}
			
				$this.data('finder-loading-next-page',false);
				
				
				this.placeholder && this.placeholder.replaceWith('<hr>');
			
			}
		};
	
		default_cycles.extend = {
			type: 'extend', // select, open, extend ...
			actions: {process: true,retrieve: true,display: true},
			cache: false
		};
	})();
	
	// ===================
	// = jQuery Function =
	// ===================	
	$.fn.finder = function(opts,params) {
		var method = (typeof opts === 'string') ? opts : null;
		opts = method ? null : opts;

		var _finder = Finders[ $(this).eq(0).data('ui-finder-id') ];

		if(method === 'path' && _finder) { return _finder.path(); }
		else if(method === 'get' && _finder) { return _finder; }
				
		return this.each(function() {
			var $this = $(this),
				finderId = $this.data('ui-finder-id') || null,
				finder = finderId && Finders[finderId];

		//	Access private methods
			if(finder && method) {
				if(params && (method === 'select' || method === 'open')) {
					finder.select(params,method); }
				else if( typeof finder[method] === 'function' ) {
					finder[method](params); } }
					
		//	Create new finder
			else if (!method) {
				finder && finder.destroy(); // not good to have two finders on same element
				var new_finder = new Finder($this,opts);
				Finders[new_finder.id] = new_finder;
				$this.data('ui-finder-id',new_finder.id); }
				 
			else if (!finder && method) {	throw new Error('jQuery Finder: Element is not a finder','');	}
		});
	};
})(jQuery);